package com.element.security.server.authorization;

import com.element.security.exception.AuthResponseException;
import com.element.security.server.config.AuthServerTokenConfig;
import com.element.security.server.config.PasswordConfig;
import com.element.security.server.config.TokenEnhancerConfig;
import com.element.security.server.config.TokenStoreConfig;
import com.element.security.server.global.GlobalOAuth2RequestFactory;
import com.element.security.server.global.GlobalOAuth2RequestValidator;
import com.element.security.server.global.token.GlobalClientTokenEndpointFilter;
import com.element.security.server.global.token.TokenGranterConfig;
import com.element.security.server.handler.AuthAccessDeniedHandler;
import com.element.security.server.handler.AuthenticationEntryPointHandler;
import com.element.security.server.service.IAuthCodeInMemoryService;
import com.element.security.server.service.IAuthCodeTokenService;
import com.element.security.server.service.IAuthUserDetailsService;
import com.element.security.server.web.AuthCodeTokenController;
import com.element.security.server.web.AuthTokenController;
import com.element.security.server.web.LoginController;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpMethod;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

/**
 * @auther zhangwj
 * @date 2021/3/10 下午8:18
 */
@Configuration
@EnableAuthorizationServer
@Import({AuthServerTokenConfig.class, IAuthCodeInMemoryService.class, PasswordConfig.class, TokenStoreConfig.class,
        TokenEnhancerConfig.class, TokenGranterConfig.class, AuthResponseException.class, GlobalOAuth2RequestValidator.class,
        AuthTokenController.class, AuthCodeTokenController.class, AuthTokenController.class, LoginController.class})
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private final static String DEFAULT_PATH_CONFIRM_ACCESS = "/oauth/confirm_access";

    private final static String CUSTOM_PATH_CONFIRM_ACCESS = "/login/confirm_access";

    @Resource(name = "authClientDetailsService")
    private ClientDetailsService clientDetailsService;

    @Resource
    private WebSecurityConfig webSecurityConfig;

    @Resource
    private IAuthUserDetailsService authUserDetailsService;

    @Resource
    private IAuthCodeInMemoryService authCodeInMemoryService;

    @Resource
    private PasswordConfig passwordConfig;

    @Resource
    private AuthServerTokenConfig authServerTokenConfig;

    @Resource
    private TokenGranter tokenGranter;

    @Resource
    private AuthResponseException authResponseException;

    @Resource
    private RestTemplate restTemplate;

    /**
     * 客户端请求配置
     *
     * @param clients 客户端
     * @throws Exception 异常
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService);
    }

    /**
     * 认证
     *
     * @param security 认证
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        GlobalClientTokenEndpointFilter endpointFilter = new GlobalClientTokenEndpointFilter(security);
        endpointFilter.afterPropertiesSet();
        endpointFilter.setAuthenticationEntryPoint(new AuthenticationEntryPointHandler());
        security.addTokenEndpointAuthenticationFilter(endpointFilter);

        // 允许表单登录(加了错误验证不能开放)
        // security.allowFormAuthenticationForClients();
        // 自定义异常处理端口
        //security.authenticationEntryPoint(authenticationEntryPoint());
        security.accessDeniedHandler(new AuthAccessDeniedHandler())
                .passwordEncoder(passwordConfig.getPasswordEncoder())
                .tokenKeyAccess("permitAll()")            //oauth/token_key是公开
                .checkTokenAccess("isAuthenticated()");   //oauth/check_token公开
    }

    /**
     * 请求地址配置
     *
     * @param endpoints 地址
     * @throws Exception 异常
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 认证管理
        endpoints.authenticationManager(webSecurityConfig.authenticationManagerBean());
        // 用户管理
        endpoints.userDetailsService(authUserDetailsService);
        // 授权码管理
        endpoints.authorizationCodeServices(authCodeInMemoryService);
        // token管理
        endpoints.tokenServices(authServerTokenConfig.getAuthServerToken(clientDetailsService));
        // 允许认证请求get和post
        endpoints.allowedTokenEndpointRequestMethods(HttpMethod.POST, HttpMethod.GET);
        // 自定义确认授权页面
        endpoints.pathMapping(DEFAULT_PATH_CONFIRM_ACCESS, CUSTOM_PATH_CONFIRM_ACCESS);
        // 自定义错误页
        // endpoints.pathMapping("/oauth/error", "https://www.baidu.com");
        endpoints.tokenGranter(tokenGranter);
        // 自定义异常
        endpoints.exceptionTranslator(authResponseException);
        // 可自定义授权通过
        // endpoints.userApprovalHandler(new ApprovalStoreUerApprovalHandler());
        //注册自定义修改client_id参数
        endpoints.requestFactory(globalOAuth2RequestFactory());
    }

    @Bean(name = "authCodeTokenService")
    public IAuthCodeTokenService authCodeTokenService() {
        IAuthCodeTokenService authCodeTokenService = new IAuthCodeTokenService();
        authCodeTokenService.setTokenGranter(tokenGranter);
        authCodeTokenService.setAuthorizationCodeServices(authCodeInMemoryService);
        authCodeTokenService.setAuthClientDetailsService(clientDetailsService);
        authCodeTokenService.setRestTemplate(restTemplate);
        authCodeTokenService.setPasswordConfig(passwordConfig);
        authCodeTokenService.setAuthorizationServerTokenServices(authServerTokenConfig.getAuthServerToken(clientDetailsService));
        return authCodeTokenService;
    }

    @Bean(name = "globalOAuth2RequestFactory")
    @ConditionalOnBean(name = "authClientDetailsService")
    public GlobalOAuth2RequestFactory globalOAuth2RequestFactory() {
        return new GlobalOAuth2RequestFactory(clientDetailsService);
    }
}
